package com.company.nuwa.redis.wrapper;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.*;
import org.springframework.lang.Nullable;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>redis zset operations wrapper</p >
 *
 * @author Nikola Tesla
 * @version 1.0
 * @date 2020/12/1 10:26
 */
@Slf4j
public class RedisZSetOperationsWrapper {

    private final ZSetOperations<String, String> zSetOperations;
    private final StringRedisTemplate stringRedisTemplate;

    public RedisZSetOperationsWrapper(StringRedisTemplate stringRedisTemplate) {
        this.zSetOperations = stringRedisTemplate.opsForZSet();
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 添加元素 （当元素已存在时，则会覆盖）
     *
     * @param key   must not be {@literal null}.
     * @param value
     * @param score
     * @return {@literal null} when used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/sadd">Redis Documentation: SADD</a>
     */
    @Nullable
    public <T> Boolean add(String key, T value, double score) {
        return zSetOperations.add(key, ValueConverter.getString(value), score);
    }

    /**
     * 添加元素 （当元素已存在时，则会覆盖）
     *
     * @param key    must not be {@literal null}.
     * @param tuples
     * @return {@literal null} when used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/sadd">Redis Documentation: SADD</a>
     */
    @Nullable
    public <T> Long add(String key, Set<ZSetOperations.TypedTuple<T>> tuples) {
        Set<ZSetOperations.TypedTuple<String>> typedTupleSet = tuples.stream()
                .map(e -> new DefaultTypedTuple<String>(ValueConverter.getString(e.getValue()), e.getScore()))
                .collect(Collectors.toSet());
        return zSetOperations.add(key, typedTupleSet);
    }

    /**
     * 添加元素 （当元素已存在时，则会覆盖）
     *
     * @param key    must not be {@literal null}.
     * @param tuples
     * @return {@literal null} when used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/sadd">Redis Documentation: SADD</a>
     */
    @Nullable
    public <T> Long add(String key, Set<ZSetOperations.TypedTuple<T>> tuples, long expiredSeconds) {
        Set<ZSetOperations.TypedTuple<String>> typedTupleSet = tuples.stream()
                .map(e -> new DefaultTypedTuple<String>(ValueConverter.getString(e.getValue()), e.getScore()))
                .collect(Collectors.toSet());
        Long result = zSetOperations.add(key, typedTupleSet);
        stringRedisTemplate.expire(key, expiredSeconds, TimeUnit.SECONDS);
        return result;
    }

    /**
     * 指定元素score的增加or减少 （当元素不存在时，则会新插入一个）
     *
     * @param key
     * @param value
     * @param incrementScore
     * @param <T>
     * @return
     */
    @Nullable
    public <T> Double incrementScore(String key, T value, double incrementScore) {
        return zSetOperations.incrementScore(key, ValueConverter.getString(value), incrementScore);
    }

    /**
     * 查询集合中指定顺序的值和score，end = -1 表示获取全部的集合内容
     * score小的在前面
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    @Nullable
    public Set<ZSetOperations.TypedTuple<String>> rangeWithScores(String key, long start, long end) {
        return zSetOperations.rangeWithScores(key, start, end);
    }

    /**
     * 匹配查询集合中匹配到的值和score （ScanOptions.NONE为获取全部；ScanOptions.scanOptions().match(“C”).build()匹配获取键位"C"的键值对,不能模糊匹配。）
     *
     * @param key
     * @param options
     * @return
     */
    @Nullable
    public List<ZSetOperations.TypedTuple<String>> scan(String key, ScanOptions options) {
        List<ZSetOperations.TypedTuple<String>> list = Lists.newArrayList();
        Cursor<ZSetOperations.TypedTuple<String>> typedTupleCursor = zSetOperations.scan(key, options);
        while (typedTupleCursor.hasNext()) {
            ZSetOperations.TypedTuple<String> next = typedTupleCursor.next();
            list.add(next);
        }
        return list;
    }

    /**
     * 查询集合中指定顺序的值和score，end = -1 表示获取全部的集合内容
     * score大的在前面
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    @Nullable
    public Set<ZSetOperations.TypedTuple<String>> reverseRangeWithScores(String key, long start, long end) {
        return zSetOperations.reverseRangeWithScores(key, start, end);
    }

    /**
     * Get set of org.springframework.data.redis.connection.RedisZSetCommands.Tuple where score is between min and max from sorted set ordered from high to low.
     * Params:
     * key – must not be null.
     * min –
     * max –
     * Returns:
     * null when used in pipeline / transaction.
     * See Also:
     * Redis Documentation: ZREVRANGEBYSCORE
     */
    @Nullable
    public Set<ZSetOperations.TypedTuple<String>> reverseRangeByScoreWithScores(String key, double start, double end) {
        return zSetOperations.reverseRangeByScoreWithScores(key, start, end);
    }

    /**
     * 获取元素个数
     *
     * @param key
     * @return
     */
    public Long count(String key) {
        return zSetOperations.zCard(key);
    }


    /**
     * 移除指定key中一个或多个元素
     *
     * @param key
     * @param values
     * @param <T>
     * @return
     */
    public <T> Long remove(String key, T... values) {
        return zSetOperations.remove(key, values);
    }

    /**
     * Get elements in range from start to end from sorted set ordered from high to low.
     * Params:
     * key – must not be null.
     * start –
     * end –
     * Returns:
     * null when used in pipeline / transaction.
     * See Also:
     * Redis Documentation: ZREVRANGE
     */
    public <T> List<T> reverseRange(String key, long start, long end, Class<T> classType) {
        Set<String> strings = zSetOperations.reverseRange(key, start, end);
        if (Objects.isNull(strings)) {
            return null;
        }
       return strings.stream()
                .map(e -> ValueConverter.getBean(e, classType))
                .collect(Collectors.toList());
    }

    /**
     * Get elements where score is between min and max from sorted set ordered from high to low.
     * Params:
     * key – must not be null.
     * min –
     * max –
     * Returns:
     * null when used in pipeline / transaction.
     * See Also:
     * Redis Documentation: ZREVRANGEBYSCORE
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public <T> List<T> reverseRangeByScore(String key, double min, double max,Class<T> classType){
        Set<String> strings = zSetOperations.reverseRangeByScore(key, min, max);
        if (Objects.isNull(strings)) {
            return null;
        }
        return strings.stream()
                .map(e -> ValueConverter.getBean(e, classType))
                .collect(Collectors.toList());
    }


}
